package com.mikufufu.blog.config;

import com.mikufufu.blog.security.filter.TokenFilter;
import com.mikufufu.blog.security.handler.AccessDecisionManagerImpl;
import com.mikufufu.blog.security.handler.FilterInvocationSecurityMetadataSourceImpl;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.access.AccessDecisionManager;
import org.springframework.security.config.annotation.ObjectPostProcessor;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.access.AccessDeniedHandler;
import org.springframework.security.web.access.intercept.FilterInvocationSecurityMetadataSource;
import org.springframework.security.web.access.intercept.FilterSecurityInterceptor;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.CorsConfigurationSource;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;

import java.util.Collections;

/**
 * Spring Security配置类
 * 
 */
@Slf4j
@Configuration // 表示这是一个配置类
@EnableWebSecurity // 启用Spring Web安全功能
@EnableGlobalMethodSecurity(prePostEnabled=true)
public class SecurityConfig {

    /**
     * 认证失败处理类：用于处理认证失败的情况。
     */
    @Autowired
    private AuthenticationEntryPoint authenticationEntryPoint;

    /**
     * 权限拒绝处理类：用于处理没有权限访问的情况。
     */
    @Autowired
    private AccessDeniedHandler accessDeniedHandler;

    /**
     * 访问决策管理器：管理访问决策，决定是否允许访问。
     * @return 返回一个AccessDecisionManager实例。
     */
    @Bean
    public AccessDecisionManager accessDecisionManager() {
        return new AccessDecisionManagerImpl();
    }

    /**
     * 安全元数据源：提供安全相关的元数据。
     * @return 返回一个FilterInvocationSecurityMetadataSource实例。
     */
    @Bean
    public FilterInvocationSecurityMetadataSource securityMetadataSource() {
        return new FilterInvocationSecurityMetadataSourceImpl();
    }

    /**
     * 配置CORS（跨源资源共享）设置，以允许来自特定源的Web请求访问应用程序。
     * 这个方法返回一个CorsConfigurationSource实例，其中包含了对跨域请求的配置。
     * @return UrlBasedCorsConfigurationSource 一个基于URL的CORS配置源，用于指定哪些URL路径应用此CORS配置。
     */
    @Bean
    public CorsConfigurationSource configurationSource(){
        // 创建一个新的CORS配置对象
        CorsConfiguration corsConfiguration = new CorsConfiguration();
        // 设置允许的所有请求头
        corsConfiguration.setAllowedHeaders(Collections.singletonList("*"));
        // 设置允许的所有请求方法
        corsConfiguration.setAllowedMethods(Collections.singletonList("*"));
        // 设置允许的请求来源，这里指定了两个来源：本地开发环境和一个示例域名
//        corsConfiguration.setAllowedOrigins(Arrays.asList("http://localhost:8080","https://www.moxiaoli.cn"));
        corsConfiguration.addAllowedOrigin("*");
        // 设置预检请求（OPTIONS请求）的缓存时间，单位为秒
        corsConfiguration.setMaxAge(3600L);
        // 创建一个新的URL基于的CORS配置源
        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
        // 将之前配置的CORS配置应用到所有URL路径
        source.registerCorsConfiguration("/**",corsConfiguration);
        // 返回配置源
        return source;
    }


    /**
     * 配置安全过滤链
     * @param http 用于配置HttpSecurity的bean
     * @return 返回配置好的SecurityFilterChain
     * @throws Exception 配置过程中可能抛出的异常
     */
    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        http
                // 配置CORS跨域资源共享
                .cors().configurationSource(configurationSource())
                .and()
                .csrf().disable() // 禁用CSRF保护
                // 禁用会话创建
                .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
                .and()
                // 禁用security默认的注销接口，以解决重定向到login页面时，因为没有登录，所以会重定向到登录页面，但是登录页面没有权限，所以会抛出异常。
                .logout().disable()
                // 配置请求过滤规则
                .authorizeRequests()
                // 使用自定义对象后处理器来配置FilterSecurityInterceptor。
                // 这个后处理器会设置拦截器的访问决策管理器和安全元数据源。
                .withObjectPostProcessor(new ObjectPostProcessor<FilterSecurityInterceptor>(){
                    /**
                     * 对给定的FilterSecurityInterceptor对象进行后处理。
                     * 主要是为了设置其访问决策管理器和安全元数据源。
                     *
                     * @param object 待处理的FilterSecurityInterceptor对象。
                     * @return 经过配置的FilterSecurityInterceptor对象。
                     */
                    @Override
                    public <O extends FilterSecurityInterceptor> O postProcess(O object) {
                        // 设置访问决策管理器
                        object.setAccessDecisionManager(accessDecisionManager());
                        // 设置安全元数据源
                        object.setSecurityMetadataSource(securityMetadataSource());
                        return object;
                    }
                } )
                .anyRequest().authenticated()// 其他所有请求需要认证
                .and()
                .addFilterBefore(new TokenFilter(), UsernamePasswordAuthenticationFilter.class);

        http.headers().frameOptions().disable();// 允许iframe访问

        http.exceptionHandling()
                // 认证失败处理
                .authenticationEntryPoint(authenticationEntryPoint)
                // 权限不足处理
                .accessDeniedHandler(accessDeniedHandler);

        // 构建并返回安全过滤链
        return http.build();
    }
}

